package com.famiover.generator.plugins;

import com.famiover.generator.Constructor;
import com.famiover.generator.constant.MybatisGeneratorConstant;
import com.famiover.generator.utils.Utils;
import com.google.common.base.CaseFormat;
import com.google.common.collect.Lists;
import org.mybatis.generator.api.CommentGenerator;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.*;

import java.util.*;

public class ExampleJavaPlugin extends PluginAdapter {

    private static boolean shareMethod = false;

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    @Override
    public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        String baseExamplePath = Utils.getPropertyDefault(context, "baseExamplePath", "com.famiover.framework.model.BaseExample");
        String objectUtilsPath = Utils.getPropertyDefault(context, "ObjectUtils", "");
        shareMethod = Objects.equals(Utils.getPropertyDefault(context, "shareMethod", "false"), "true");
        addGetFiledNameMethod(introspectedTable, topLevelClass);
        addCriteriaGetter(topLevelClass, introspectedTable);
        final String baseName = Utils.getBaseName(baseExamplePath);
        topLevelClass.setSuperClass(baseName);
        topLevelClass.addImportedType(baseExamplePath);
        topLevelClass.addImportedType(objectUtilsPath);
        configExample(topLevelClass, introspectedTable);
        boolean result = super.modelExampleClassGenerated(topLevelClass, introspectedTable);
        Utils.addAuthor(topLevelClass);
        return result;
    }

    private void configExample(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        topLevelClass.addMethod(getMethodFirst());
        topLevelClass.addMethod(getMethodSecond());
        Method methodThird = getMethodThird(topLevelClass, introspectedTable);
        if (Objects.nonNull(methodThird)) {
            topLevelClass.addMethod(methodThird);
        }
        addOverrideToSetOrderByClause(topLevelClass);
    }


    private void addOverrideToSetOrderByClause(TopLevelClass topLevelClass) {
        List<Method> methods = topLevelClass.getMethods();
        if (methods != null && methods.size() > 0) {
            for (Method method : methods) {
                if ("setOrderByClause".equals(method.getName())) {
                    method.addAnnotation("@Override");
                }
            }
        }

    }

    private void addMethodNullOrEmptyString(InnerClass innerClass) {
        Method method = new Method();

        method.setVisibility(JavaVisibility.PRIVATE);
        method.setReturnType(FullyQualifiedJavaType.getBooleanPrimitiveInstance());
        method.setName("nullOrEmptyString");
        method.addParameter(new Parameter(FullyQualifiedJavaType.getObjectInstance(), "value"));

        List<String> stringList = Lists.newArrayList();
        stringList.add("return isEmpty(value);");
        method.addBodyLines(stringList);
        innerClass.addMethod(method);
    }



    private void addMethodNullOrEmptyString1(InnerClass innerClass) {
        Method method = new Method();

        method.setVisibility(JavaVisibility.PRIVATE);
        method.setReturnType(FullyQualifiedJavaType.getBooleanPrimitiveInstance());
        method.setName("nullOrEmptyString");
        method.addParameter(new Parameter(FullyQualifiedJavaType.getObjectInstance(), "value"));

        List<String> stringList = Lists.newArrayList();
        stringList.add("if (value == null) {");
        stringList.add("return true;");
        stringList.add("}");
        stringList.add("if (value instanceof String) {");
        stringList.add("String string = value.toString();");
        stringList.add("return string.trim().length() == 0;");
        stringList.add("}");
        stringList.add("return false;");
        method.addBodyLines(stringList);
        innerClass.addMethod(method);
    }

    private void addThreeParameter(Method method) {
        List<String> bodyLines = method.getBodyLines();
        bodyLines.clear();
        bodyLines.add("if(nullOrEmptyString(value)){");
        bodyLines.add("if(skipNull){");
        bodyLines.add("return;");
        bodyLines.add("}");
        bodyLines.add("throw new RuntimeException(\"Value for \" + property + \" cannot be null\");");
        bodyLines.add("}");
        bodyLines.add("criteria.add(new Criterion(condition, value));");
    }

    private void addFourParameter(Method method) {
        List<String> bodyLines = method.getBodyLines();
        bodyLines.clear();
        bodyLines.add("if(nullOrEmptyString(value1) || nullOrEmptyString(value2)){");
        bodyLines.add("if(skipNull){");
        bodyLines.add("return;");
        bodyLines.add("}");
        bodyLines.add("throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");");
        bodyLines.add("}");
        bodyLines.add("criteria.add(new Criterion(condition, value1, value2));");
    }


    private <T> boolean isEmpty(List<T> cols) {
        if (Objects.isNull(cols)) {
            return true;
        }
        return cols.size() == 0;
    }


    private void addGetFiledNameMethod(IntrospectedTable introspectedTable, TopLevelClass topLevelClass) {
        List<IntrospectedColumn> primaryKeyColumns = introspectedTable.getPrimaryKeyColumns();
        if (isEmpty(primaryKeyColumns)) {
            return;
        }
        List<String> stringList = Lists.newArrayList();
        for (IntrospectedColumn primaryKeyColumn : primaryKeyColumns) {
            String actualColumnName = primaryKeyColumn.getActualColumnName();
            stringList.add(actualColumnName);
        }
        List<IntrospectedColumn> baseColumns = introspectedTable.getBaseColumns();
        for (IntrospectedColumn baseColumn : baseColumns) {
            String actualColumnName = baseColumn.getActualColumnName();
            stringList.add(actualColumnName);
        }
        for (String col : stringList) {
            addMethod(col, topLevelClass);
        }
        addColumnsListMethod(stringList, topLevelClass);
//        addExcludeMethod(stringList, topLevelClass);
    }


    private void addColumnsListMethod(List<String> colList, TopLevelClass topLevelClass) {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.addAnnotation("@Override");
        method.setName("getColumnList");

        FullyQualifiedJavaType paramType = FullyQualifiedJavaType.getNewListInstance();
        FullyQualifiedJavaType paramListType = FullyQualifiedJavaType.getStringInstance();
        paramType.addTypeArgument(paramListType);
        method.setReturnType(paramType);

        List<String> lines = new ArrayList<>();
        String fieldsLine = "List<String> fields = new ArrayList<>();";
        lines.add(fieldsLine);
        for (String col : colList) {
            String filedItemLine = "fields.add(" + MybatisGeneratorConstant.DOUBLE_QUOTATION_MARKS + col + MybatisGeneratorConstant.DOUBLE_QUOTATION_MARKS + ");";
            lines.add(filedItemLine);
        }
        lines.add("return fields;");
        method.addBodyLines(lines);
        topLevelClass.addMethod(method);
    }

    private void addExcludeMethod(List<String> colList, TopLevelClass topLevelClass) {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setName("setExcludeColumns");
        method.addParameter(new Parameter(new FullyQualifiedJavaType("String..."), "excludeCols"));

        List<String> lines = new ArrayList<>();
        lines.add("if (excludeCols == null) {");
        lines.add("return;");
        lines.add("}");
        String fieldsLine = "List<String> fields = getColumnList();";
        lines.add(fieldsLine);
        lines.add("List<String> excludeList = Arrays.stream(excludeCols).collect(Collectors.toList());");
        lines.add("for (String excludeField : excludeList) {");
        lines.add("fields.remove(excludeField);");
        lines.add("}");
        lines.add("setColumns(fields);");
        method.addBodyLines(lines);
        topLevelClass.addMethod(method);
        topLevelClass.addImportedType("java.util.Arrays");
        topLevelClass.addImportedType("java.util.stream.Collectors");
    }

    private void addMethod(String col, TopLevelClass topLevelClass) {
        String to = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, col);
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setName("getField" + to);
        method.setStatic(true);
        method.setReturnType(FullyQualifiedJavaType.getStringInstance());
        String ret = "\"" + col + "\"";
        method.addBodyLine("return " + ret + ";");
        topLevelClass.addMethod(method);
    }


    private Method getMethodThird(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        List<InnerClass> innerClasses = topLevelClass.getInnerClasses();
        for (InnerClass innerClass : innerClasses) {
            if (innerClass.getType().getShortName().equals("Criteria")) {
                Constructor constructor = new Constructor();
                constructor.addBodyLine("public Criteria(boolean skipNull){");
                constructor.addBodyLine("super(skipNull);");
                constructor.addBodyLine("}");
                innerClass.addInitializationBlock(constructor);
            }
            if (innerClass.getType().getShortName().equals("GeneratedCriteria")) {
                addMethodNullOrEmptyString(innerClass);
                topLevelClass.addImportedType("");

                Field field = new Field();
                field.setName("skipNull=false");
                field.setType(FullyQualifiedJavaType.getBooleanPrimitiveInstance());
                field.setVisibility(JavaVisibility.PRIVATE);
                innerClass.addField(field);

                Constructor constructor = new Constructor();
                constructor.addBodyLine("public GeneratedCriteria(boolean skipNull) {");
                constructor.addBodyLine("super();");
                constructor.addBodyLine("this.skipNull=skipNull;");
                constructor.addBodyLine("criteria = new ArrayList<Criterion>();");
                constructor.addBodyLine("}");
                innerClass.addInitializationBlock(constructor);

                List<Method> methods = innerClass.getMethods();
                Iterator<Method> it = methods.iterator();
                while (it.hasNext()) {
                    Method method = it.next();
                    String name = method.getName();
                    if (name.startsWith("and")) {
                        if (shareMethod) {
                            it.remove();
                        }
                    }
                    if (Objects.equals(name, "addCriterion")) {
                        method.setVisibility(JavaVisibility.PUBLIC);
                        List<Parameter> parameters = method.getParameters();
                        int size = size(parameters);
                        if (size == 3) {
                            addThreeParameter(method);
                        }
                        if (size == 4) {
                            addFourParameter(method);
                        }
                    }
                }
                addCommonMethod(innerClass);
            }
        }
        return null;
    }

    private void addCommonMethod(InnerClass innerClass) {
        if (!shareMethod) {
            return;
        }
        innerClass.addMethod(getIsNotNullMethod());
        innerClass.addMethod(getIsNullMethod());
        innerClass.addMethod(getEqualMethod());
        innerClass.addMethod(getNotEqualMethod());
        innerClass.addMethod(getGreaterThanMethod());
        innerClass.addMethod(getGreaterThanOrEqualToMethod());
        innerClass.addMethod(getLessThanMethod());
        innerClass.addMethod(getLessThanOrEqualToMethod());
        innerClass.addMethod(getInMethod());
        innerClass.addMethod(getNotInMethod());
        innerClass.addMethod(getBetweenMethod());
        innerClass.addMethod(getNotBetweenMethod());
    }

    private Method getIsNotNullMethod() {
        return getNoneParamMethod("andIsNotNull", "addCriterion(fieldName+\" is not null\");");
    }

    private Method getIsNullMethod() {
        return getNoneParamMethod("andIsNull", "addCriterion(fieldName+\" is null\");");
    }

    private Method getEqualMethod() {
        return getOneParamMethod("andEqualTo", "=");
    }

    private Method getNotEqualMethod() {
        return getOneParamMethod("andNotEqualTo", "<>");
    }

    private Method getGreaterThanMethod() {
        return getOneParamMethod("andGreaterThan", ">");
    }

    private Method getGreaterThanOrEqualToMethod() {
        return getOneParamMethod("andGreaterThanOrEqualTo", ">=");
    }

    private Method getLessThanMethod() {
        return getOneParamMethod("LessThan", "<");
    }

    private Method getLessThanOrEqualToMethod() {
        return getOneParamMethod("andLessThanOrEqualTo", "<=");
    }

    private Method getInMethod() {
        return getOneParamMethod("andIn", "in");
    }

    private Method getNotInMethod() {
        return getOneParamMethod("andNotIn", "not in");
    }

    private Method getBetweenMethod() {
        return getTwoParamMethod(FullyQualifiedJavaType.getDateInstance(), "andBetween", "between");
    }

    private Method getNotBetweenMethod() {
        return getTwoParamMethod(FullyQualifiedJavaType.getDateInstance(), "andNotBetween", "not between");
    }

    private String addSpaceString(String opt) {
        return " " + opt;
    }

    private Method getNoneParamMethod(String methodName, String methodBody) {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(new FullyQualifiedJavaType("Criteria"));
        method.setName(methodName);
        method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "fieldName"));
        method.addBodyLine(methodBody);
        method.addBodyLine("return (Criteria) this;");
        return method;
    }

    private Method getOneParamMethod(String methodName, String opt) {
        opt = addSpaceString(opt);
        String methodBody = "addCriterion(fieldName + %s, value, fieldName);";
        methodBody = String.format(methodBody, "\"" + opt + "\"");
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(new FullyQualifiedJavaType("Criteria"));
        method.setName(methodName);
        method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "fieldName"));
        method.addParameter(new Parameter(FullyQualifiedJavaType.getObjectInstance(), "value"));
        method.addBodyLine(methodBody);
        method.addBodyLine("return (Criteria) this;");
        return method;
    }

    private Method getTwoParamMethod(FullyQualifiedJavaType fullyQualifiedJavaType, String methodName, String opt) {
        opt = addSpaceString(opt);
        String methodBody = "addCriterion(fieldName + %s, value1, value2, fieldName);";
        methodBody = String.format(methodBody, "\"" + opt + "\"");
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(new FullyQualifiedJavaType("Criteria"));
        method.setName(methodName);
        method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "fieldName"));
        method.addParameter(new Parameter(fullyQualifiedJavaType, "value1"));
        method.addParameter(new Parameter(fullyQualifiedJavaType, "value2"));
        method.addBodyLine(methodBody);
        method.addBodyLine("return (Criteria) this;");
        return method;
    }

    /**
     * 获取集合的Size
     *
     * @param collection 集合
     */
    public static <T> int size(Collection<T> collection) {
        if (Objects.isNull(collection)) {
            return 0;
        }
        return collection.size();
    }


    private Method getMethodFirst() {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(new FullyQualifiedJavaType("Criteria"));
        method.setName("createCriteria");
        method.addParameter(new Parameter(FullyQualifiedJavaType.getBooleanPrimitiveInstance(), "skipNull"));
        method.addBodyLine("Criteria criteria = createCriteriaInternal(skipNull);");
        method.addBodyLine("if (oredCriteria.size() == 0) {");
        method.addBodyLine("oredCriteria.add(criteria);");
        method.addBodyLine("}");
        method.addBodyLine("return criteria;");
        return method;
    }

    private Method getMethodSecond() {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(new FullyQualifiedJavaType("Criteria"));
        method.setName("createCriteriaInternal");
        method.addParameter(new Parameter(FullyQualifiedJavaType.getBooleanPrimitiveInstance(), "skipNull"));
        method.addBodyLine("Criteria criteria = new Criteria(skipNull);");
        method.addBodyLine("return criteria;");
        return method;
    }

    private void addCriteriaGetter(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        CommentGenerator commentGenerator = context.getCommentGenerator();
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setName("getCriteria");
        method.setReturnType(new FullyQualifiedJavaType("Criteria"));
        method.addBodyLine("if (oredCriteria.size() != 0) {return oredCriteria.get(0);}");
        method.addBodyLine("Criteria criteria = createCriteriaInternal();");
        method.addBodyLine("oredCriteria.add(criteria);");
        method.addBodyLine("return criteria;");
        commentGenerator.addGeneralMethodComment(method, introspectedTable);
        topLevelClass.addMethod(method);
    }

}
